package com.letoken.platform.pub.util;

import net.sf.cglib.beans.BeanCopier;
import org.apache.commons.lang3.StringUtils;
import org.dozer.DozerBeanMapper;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * BeanUtil
 */
@Component
public class BeanUtil implements ApplicationContextAware {
    private final static String DEFAULT_CHARSET = "UTF-8";
    private static ApplicationContext applicationContext;

    /**
     * 持有Dozer单例, 避免重复创建DozerMapper消耗资源.
     */
    private static DozerBeanMapper dozer = new DozerBeanMapper();

    private final static Map<Class<?>, Map<Class<?>, BeanCopier>> beanCopiers = new ConcurrentHashMap<Class<?>, Map<Class<?>, BeanCopier>>(
            128);

    public static void eachProperties(Object bean, EachPropertiesHandler eachPropertiesHandler) {
        eachPropertiesInner(bean.getClass(), bean, eachPropertiesHandler);
    }


    public static Map<String, Object> beanToMap(Object bean) {
        Map<String, Object> map = new HashMap<String, Object>();
        beanToMapInner(bean.getClass(), bean, map);
        return map;
    }


    public static String beanToQueryString(Object bean, String dateFormat, String charset) {
        StringBuffer string = new StringBuffer();
        beanToQueryStringInner(bean.getClass(), string, bean, dateFormat, charset);
        if (string.length() > 0) {
            string.delete(0, 1);
        }
        return string.toString();
    }


    public static String beanToQueryString(Object bean, String dateFormat) {
        return beanToQueryString(bean, dateFormat, DEFAULT_CHARSET);
    }


    public static String beanToQueryString(Object bean) {
        return beanToQueryString(bean, "yyyy-MM-dd", DEFAULT_CHARSET);
    }


    public static <T> T quickMap(Object source, Class<T> targetClass) {
        if (source == null) {
            return null;
        }
        BeanCopier beanCopier = getBeanCopier(source.getClass(), targetClass);

        T to = null;

        try {
            to = targetClass.newInstance();
        } catch (InstantiationException e) {
            throw new UnsupportedOperationException("Class " + targetClass + "not hava constructor is no params", e);
        } catch (IllegalAccessException e) {
            throw new UnsupportedOperationException("Class " + targetClass + "not hava constructor is no params", e);
        }

        beanCopier.copy(source, to, null);
        return to;
    }


    public static <T> List<T> quickMapList(Collection<?> sourceList, Class<T> targetClass) {
        List<T> targetList = new ArrayList<T>();
        if (sourceList.isEmpty()) {
            return targetList;
        }

        Iterator<?> it = sourceList.iterator();
        BeanCopier beanCopier = getBeanCopier(it.next().getClass(), targetClass);
        try {
            for (Object source : sourceList) {
                T target;
                target = targetClass.newInstance();
                beanCopier.copy(source, target, null);
                targetList.add(target);
            }
        } catch (Exception e) {
            throw new UnsupportedOperationException("Class " + targetClass + "not hava constructor is no params", e);
        }
        return targetList;
    }

    /**
     * 对象快速拷贝
     */
    public static void quickCopy(Object source, Object target) {
        AssertUtil.notNull(source);
        AssertUtil.notNull(target);
        BeanCopier beanCopier = getBeanCopier(source.getClass(), target.getClass());
        beanCopier.copy(source, target, null);
    }

    /**
     * 对象深拷贝
     */
    public static <T> T adviceMap(Object source, Class<T> destinationClass) {
        if (source == null) {
            return null;
        }
        return dozer.map(source, destinationClass);
    }

    /**
     * 对象深拷贝
     */
    public static <T> List<T> adviceMapList(Collection<?> sourceList, Class<T> destinationClass) {
        List<T> destinationList = new ArrayList<T>();
        for (Object sourceObject : sourceList) {
            T destinationObject = dozer.map(sourceObject, destinationClass);
            destinationList.add(destinationObject);
        }
        return destinationList;
    }

    /**
     * 对象深拷贝
     */
    public static void adviceCopy(Object source, Object destinationObject) {
        AssertUtil.notNull(source);
        AssertUtil.notNull(destinationObject);
        dozer.map(source, destinationObject);
    }

    /**
     * 获取一个对象的对象名
     *
     * @param po objectName
     */
    public static String getClassName(Object po) {
        String returnString = StringUtils.capitalize(po.getClass().getSimpleName());
        return returnString;
    }

    /**
     * 获取一个类的对象名
     *
     * @param c objectName
     */
    public static String getObjectName(Class<?> c) {
        String returnString = StringUtils.uncapitalize(c.getSimpleName());
        return returnString;
    }

    private static void beanToMapInner(Class<?> thisClass, Object bean, Map<String, Object> map) {
        Method[] methods = thisClass.getDeclaredMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.length() > 3 && methodName.startsWith("get")) {
                pubValueToMap(bean, map, method, 3);
            } else if (methodName.length() > 2 && methodName.startsWith("is")) {
                pubValueToMap(bean, map, method, 2);
            }
        }
        Class<?> superClass = thisClass.getSuperclass();
        if (superClass != null && !Object.class.equals(superClass)) {
            beanToMapInner(superClass, bean, map);
        }
    }

    private static void pubValueToMap(Object bean, Map<String, Object> map, Method method, int methedNamePrefixLength) {
        Object value = invoke(bean, method);
        if (value != null) {
            String methodName = method.getName();
            String fieldName = StringUtils.uncapitalize(StringUtils.substring(methodName, methedNamePrefixLength));
            map.put(fieldName, value);
        }
    }

    private static void pubValueToString(Object bean, StringBuffer string, Method method, int methedNamePrefixLength,
                                         String dateFormat, String charset) {
        Object value = invoke(bean, method);
        if (value != null) {
            boolean tag = true;
            if (Boolean.class.isInstance(value)) {
                tag = Boolean.TRUE.equals(value);
            }
            if (tag) {
                String methodName = method.getName();
                String fieldName = StringUtils.uncapitalize(StringUtils.substring(methodName, methedNamePrefixLength));

                if (Date.class.isInstance(value)) {
                    value = DateUtil.format((Date) value, dateFormat);
                }
                try {
                    value = URLEncoder.encode(value + "", charset);
                } catch (UnsupportedEncodingException e) {
                    throw new UnsupportedOperationException("unsupported encoding exception", e);
                }

                string.append("&" + fieldName + "=" + value);
            }
        }
    }

    private static void beanToQueryStringInner(Class<?> thisClass, StringBuffer string, Object bean, String dateFormat,
                                               String charset) {
        Method[] methods = thisClass.getDeclaredMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.length() > 3 && methodName.startsWith("get")) {
                pubValueToString(bean, string, method, 3, dateFormat, charset);
            } else if (methodName.length() > 2 && methodName.startsWith("is")) {
                pubValueToString(bean, string, method, 2, dateFormat, charset);
            }
        }

        Class<?> superClass = thisClass.getSuperclass();
        if (superClass != null && !Object.class.equals(superClass)) {
            beanToQueryStringInner(superClass, string, bean, dateFormat, charset);
        }
    }

    private static BeanCopier getBeanCopier(Class<?> sourceClass, Class<?> targetClass) {
        Map<Class<?>, BeanCopier> mapInner = beanCopiers.get(sourceClass);
        BeanCopier beanCopier = null;
        if (mapInner == null) {
            mapInner = new ConcurrentHashMap<Class<?>, BeanCopier>(128);
            beanCopier = BeanCopier.create(sourceClass, targetClass, false);
            mapInner.put(targetClass, beanCopier);
            beanCopiers.put(sourceClass, mapInner);
        } else {
            beanCopier = mapInner.get(targetClass);
            if (beanCopier == null) {
                beanCopier = BeanCopier.create(sourceClass, targetClass, false);
                mapInner.put(targetClass, beanCopier);
            }
        }
        return beanCopier;
    }

    private static void eachPropertiesInner(Class<?> thisClass, Object bean, EachPropertiesHandler eachPropertiesHandler) {
        Method[] methods = thisClass.getDeclaredMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.length() > 3 && methodName.startsWith("get")) {
                pubValueToHandler(bean, method, 3, eachPropertiesHandler);
            } else if (methodName.length() > 2 && methodName.startsWith("is")) {
                pubValueToHandler(bean, method, 2, eachPropertiesHandler);
            }
        }
        Class<?> superClass = thisClass.getSuperclass();
        if (superClass != null && !Object.class.equals(superClass)) {
            eachPropertiesInner(superClass, bean, eachPropertiesHandler);
        }
    }

    private static void pubValueToHandler(Object bean, Method method, int methedNamePrefixLength,
                                          EachPropertiesHandler eachPropertiesHandler) {
        Object value = invoke(bean, method);

        if (value != null) {
            String methodName = method.getName();
            String fieldName = StringUtils.uncapitalize(StringUtils.substring(methodName, methedNamePrefixLength));
            eachPropertiesHandler.handler(fieldName, value);
        }
    }

    private static Object invoke(Object bean, Method method) {
        Object value = null;
        try {
            value = method.invoke(bean);
        } catch (IllegalAccessException e) {
            throw new UnsupportedOperationException(e);
        } catch (InvocationTargetException e) {
            throw new UnsupportedOperationException(e.getTargetException().getMessage(), e);
        }
        return value;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class cls) {
        return (T) applicationContext.getBean(cls);
    }

    public interface EachPropertiesHandler {
        void handler(String property, Object value);
    }

}
